
// Copyright (C) 2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;

namespace Derm.Render
{
	/// <summary>
	/// 
	/// </summary>
	public class ShaderCacheService : RenderKernelService
	{
		#region Constructors

		/// <summary>
		/// Construct a ShaderCacheService.
		/// </summary>
		public ShaderCacheService() : base(ThisServiceName, ExecutionModel.None)
		{
			
		}

		/// <summary>
		/// This service name.
		/// </summary>
		private static readonly string ThisServiceName = "Derm.Render.ShaderCacheService";

		#endregion

		#region Service Access

		/// <summary>
		/// Get the ShaderCacheService for the specified object name space.
		/// </summary>
		/// <param name="rObjectNameSpace">
		/// A <see cref="System.Guid"/>
		/// </param>
		/// <returns>
		/// It returns a ShaderCacheService which handle objects generated in the name space <paramref name="rObjectNameSpace"/>.
		/// </returns>
		public static ShaderCacheService GetService()
		{
			RenderContext ctx = RenderContext.GetCurrentContext();

			if (ctx == null)
				throw new InvalidOperationException("no current context");

			return (GetService(ctx));
		}

		/// <summary>
		/// Get the ShaderCacheService for the specified object name space.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> that will be used to create
		/// </param>
		/// <returns>
		/// It returns a ShaderCacheService which handle objects generated in the name space of <paramref name="ctx"/>.
		/// </returns>
		public static ShaderCacheService GetService(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");

			return ((ShaderCacheService)GetService(ctx.ObjectNameSpace, ThisServiceName));
		}

		#endregion

		#region Shader Object Cache

		/// <summary>
		/// Create and cache shader object.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating the shader object.
		/// </param>
		/// <param name="cctx">
		/// A <see cref="ShaderCompilerContext"/> used for creating the shader object.
		/// </param>
		/// <param name="identifier">
		/// A <see cref="System.String"/> that specifies the shader object library identifier.
		/// </param>
		/// <param name="stage">
		/// A <see cref="ShaderObject.Stage"/> that specifies the shader object stage.
		/// </param>
		/// <returns>
		/// It returns a <see cref="ShaderProgram"/>. Possibly, returned shader program could be cached from a previous
		/// creation.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="ctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// This exception is thrown if <paramref name="ctx"/> is not current.
		/// </exception>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="cctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="identifier"/> is null.
		/// </exception>
		public ShaderObject CreateShaderObject(RenderContext ctx, ShaderCompilerContext cctx, string identifier, ShaderObject.Stage stage)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.IsCurrent() == false)
				throw new ArgumentException("not current", "ctx");
			if (cctx == null)
				throw new ArgumentNullException("cctx");
			if (identifier == null)
				throw new ArgumentNullException("identifier");

			ShaderObject sObject = null;
			string sObjectCacheId = ShaderObject.ComputeCompilerHash(ctx, cctx, identifier, stage);

			// Look for cached shader object
			lock (sShaderObjectCacheLock) {
				if (mShaderObjectCache.ContainsKey(sObjectCacheId) == true)
					sObject = mShaderObjectCache[sObjectCacheId];
			}

			if (sObject == null) {

				// Not cached: create a new instance

				sObject = ShaderLibrary.CreateShaderObject(ctx, cctx, identifier, stage);

				// Cache shader object
				lock (sShaderObjectCacheLock) {
					mShaderObjectCache[sObjectCacheId] = sObject;
				}
			}

			return (sObject);
		}

		/// <summary>
		/// Cached shader objects.
		/// </summary>
		/// <remarks>
		/// <para>
		/// The key value identifies the compiled object. It is a string resulted by hashing all that information influencing
		/// the compilation result. The value is the once returned by <see cref="ShaderObject.ComputeCompilerHash"/> method.
		/// </para>
		/// <para>
		/// The value specify the cached ShaderObject.
		/// </para>
		/// </remarks>
		private Dictionary<string, ShaderObject> mShaderObjectCache = new Dictionary<string,ShaderObject>();

		/// <summary>
		/// Object for synchronized access to 'mShaderObjectCache'.
		/// </summary>
		private object sShaderObjectCacheLock = new object();

		#endregion

		#region Shader Program Cache

		/// <summary>
		/// Create and cache shader program.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating the shader program.
		/// </param>
		/// <param name="cctx">
		/// A <see cref="ShaderCompilerContext"/> used for creating the shader program.
		/// </param>
		/// <param name="identifier">
		/// A <see cref="System.String"/> that specifies the shader program library identifier.
		/// </param>
		/// <returns>
		/// It returns a <see cref="ShaderProgram"/>. Possibly, returned shader program could be cached from a previous
		/// creation.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="ctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// This exception is thrown if <paramref name="ctx"/> is not current.
		/// </exception>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="cctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="identifier"/> is null.
		/// </exception>
		public ShaderProgram CreateShaderProgram(RenderContext ctx, ShaderCompilerContext cctx, string identifier)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.IsCurrent() == false)
				throw new ArgumentException("not current", "ctx");
			if (cctx == null)
				throw new ArgumentNullException("cctx");
			if (identifier == null)
				throw new ArgumentNullException("identifier");

			ShaderProgram program = null;
			string programCacheId = ShaderProgram.ComputeLibraryHash(ctx, cctx, identifier);

			// Look for cached shader object
			lock (sShaderProgramCacheLock) {
				if (mShaderProgramCache.ContainsKey(programCacheId) == true)
					program = mShaderProgramCache[programCacheId];
			}

			if (program == null) {

				// Not cached: create a new instance

				program = ShaderLibrary.CreateShaderProgram(ctx, cctx, identifier);

				// Cache shader program
				lock (sShaderProgramCacheLock) {
					mShaderProgramCache[programCacheId] = program;
				}
			}

			return (program);
		}

		/// <summary>
		/// Cached shader programs.
		/// </summary>
		/// <remarks>
		/// <para>
		/// The key value identifies the compiled object. It is a string resulted by hashing all that information influencing
		/// the compilation result. The value is the once returned by <see cref="ShaderObject.ComputeLibraryHash"/> method.
		/// </para>
		/// <para>
		/// The value specify the cached ShaderProgram.
		/// </para>
		/// </remarks>
		private Dictionary<string, ShaderProgram> mShaderProgramCache = new Dictionary<string,ShaderProgram>();

		/// <summary>
		/// Object for synchronized access to 'mShaderProgramCache'.
		/// </summary>
		private object sShaderProgramCacheLock = new object();

		#endregion

		#region Shader Includes

		/// <summary>
		/// 
		/// </summary>
		private List<IShaderInclude> mShaderIncludes = new List<IShaderInclude>();

		#endregion
	}
}
